package com.auth.domain.service.impl;

import com.alibaba.fastjson.JSON;
import com.redis.sdk.RedisClusterUtils;
import com.redis.sdk.cachecore.CacheRedis;
import com.auth.common.constant.RedisKey;
import com.auth.common.util.IPageUtil;
import com.auth.common.util.security.attestation.JWTUtil;
import com.auth.common.util.security.secrecy.Secrecy;
import com.auth.sdk.LoginInfo;
import com.auth0.jwt.interfaces.Claim;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.auth.common.exception.ErrorCode;
import com.auth.control.model.UserDto;
import com.auth.domain.mapper.authmanage.UserMapper;
import com.auth.domain.module.vo.authmanage.*;
import com.auth.domain.service.*;
import com.auth.domain.module.po.authmanage.User;
import com.bean.ObjectMapperUtils;
import com.redis.sdk.cachecore.LockObject;
import com.common.global.GlobalException;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * User service
 *
 * @description:
 * @author: cyj
 * @date 2020 -10-26 08:51:17
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private IUserRoleService userRoleService;

    @Autowired
    private IRoleAuthService roleAuthService;

    @Autowired
    private Secrecy MD5;

    @Autowired
    private Secrecy RSA;

    @Autowired
    private RedisClusterUtils redisClusterUtils;

    private Long tokenTime = 1000L * 60 * 30;

    @Autowired
    private IUserService userService;

    @Value("${token.auto-refresh:false}")
    private Boolean tokenAutoRefresh;

    @Override
    public User getByLoginId(String loginId) {
        User condition = new User().setLoginId(loginId);
        return this.baseMapper.selectOne(new QueryWrapper<User>(condition));
    }

    @Override
    @CacheRedis(value = "#id", prefix = RedisKey.AUTHCACHE)
    public UserAuthVo getAuth(Long id) {
        User user = Optional.ofNullable(this.getById(id)).orElseThrow(() -> new GlobalException(ErrorCode.USER_NOT_FOUND));
        UserAuthVo userAuthVo = ObjectMapperUtils.clone(user, UserAuthVo.class);
        userAuthVo.setRoles(this.assembleRoleAuths(id));
        return userAuthVo;
    }

    @Override
    public List<RoleAuthsVo> assembleRoleAuths(Long id) {
        List<RoleAuthsVo> roleAuthsVos = ObjectMapperUtils.clones(userRoleService.getUserRoles(id), RoleAuthsVo.class);
        // 批量初始化auths
        roleAuthsVos.parallelStream().forEach(roleAuthsVo -> {
            List<AuthVO> roleAuths = roleAuthService.getRoleAuths(roleAuthsVo.getId());
            roleAuthsVo.setAuth(roleAuths);
        });
        return roleAuthsVos;
    }

    @Override
    public IPage<UserVO> pageCommon(Page page) {
        IPage<User> result = this.page(page);
        return IPageUtil.copy(result, UserVO.class);
    }

    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public UserVO saveUser(UserDto userDto) {
        UserVO userVO = selectUser(userDto.getLoginId());
        if (null != userVO) {
            throw new RuntimeException("用户已经存在");
        }
        userDto.setPassword(MD5.encrypt(userDto.getPassword()));
        User user = ObjectMapperUtils.clone(userDto, User.class);
        this.save(user);

        return ObjectMapperUtils.clone(user, UserVO.class);
    }

    @Override
    public UserVO updateUser(UserDto userDto) {
        UserVO userVO = selectUser(userDto.getLoginId());
        if (null != userVO && !userDto.getLoginId().equals(userVO.getLoginId())) {
            throw new RuntimeException("用户已经存在");
        }
        userDto.setPassword(MD5.encrypt(userDto.getPassword()));
        User user = ObjectMapperUtils.clone(userDto, User.class);
        this.updateById(user);
        return this.selectUser(userDto.getId());
    }

    @Override
    public UserVO selectUser(Long id) {
        User user = this.getById(id);
        return ObjectMapperUtils.clone(user, UserVO.class);
    }

    @Override
    public UserVO selectUser(String loginId) {
        LambdaQueryWrapper<User> lambdaQueryWrapper = Wrappers.lambdaQuery();
        lambdaQueryWrapper.eq(User::getLoginId, loginId);
        User user = this.getOne(lambdaQueryWrapper, false);
        return ObjectMapperUtils.clone(user, UserVO.class);
    }

    @Override
    public UserVO deleteUser(Long id) {
        UserVO userVO = this.selectUser(id);
        this.removeById(id);
        return userVO;
    }

    @Override
    public String authenticationIn(String loginId, String pwd) {
        UserVO userVO = Optional.ofNullable(this.selectUser(loginId)).orElseThrow(() -> new GlobalException("账号不存在!"));
        String encrypt = MD5.encrypt(RSA.decrypt(pwd));
        if (!userVO.getPassword().equals(encrypt)) {
            throw new GlobalException("密码不正确！");
        }
        LoginInfo loginInfo = new LoginInfo();
        loginInfo.setUserId(userVO.getId().toString());
        loginInfo.setUserName(userVO.getName());
        String token = JWTUtil.createToken(JSON.toJSONString(loginInfo), 1);
        String key = String.format(RedisKey.TOKEN, userVO.getId());
        redisClusterUtils.set(key, token, tokenTime);
        return token;
    }

    @Override
    public void authenticationOut(String id, String token) {
        String key = String.format(RedisKey.TOKEN, id);
        redisClusterUtils.unlock(new LockObject(key, token));
    }

    @Override
    public LoginInfo authenticationInfo(String token) {
        Map<String, Claim> stringClaimMap = JWTUtil.parseToken(token);
        if (!stringClaimMap.containsKey(JWTUtil.INFO)) {
            throw new RuntimeException("token 非法");
        }
        String info = stringClaimMap.get(JWTUtil.INFO).asString();
        LoginInfo loginInfo = ObjectMapperUtils.clone(JSON.parse(info), LoginInfo.class);
        if (null == loginInfo) {
            throw new RuntimeException("token 非法");
        }
        String key = String.format(RedisKey.TOKEN, loginInfo.getUserId());
        Object redisToken = redisClusterUtils.get(key);
        if (null == redisToken || !redisToken.equals(token)) {
            throw new RuntimeException("token 已经过期");
        }
        // 刷新token
        if (tokenAutoRefresh) {
            redisClusterUtils.set(key, token, tokenTime);
        }
        UserAuthVo auth = userService.getAuth(Long.valueOf(loginInfo.getUserId()));

        Set<String> auths = new HashSet<>();

        auth.getRoles().stream().forEach(roleAuthsVo -> {
            List<String> collect = roleAuthsVo.getAuth().stream().map(AuthVO::getAuth).collect(Collectors.toList());
            auths.addAll(collect);
        });
        loginInfo.setRoles(auth.getRoles().stream().map(RoleAuthsVo::getRoleName).distinct().collect(Collectors.toList()));
        loginInfo.setAuths(new ArrayList<>(auths));
        return loginInfo;
    }
}
